/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.graphics.surface

import android.graphics.Rect
import android.graphics.Region
import android.hardware.HardwareBuffer
import android.os.Build
import android.view.Surface
import android.view.SurfaceControl
import androidx.annotation.RequiresApi
import androidx.graphics.utils.JniVisible
import androidx.hardware.SyncFenceV19
import java.util.concurrent.Executor

@JniVisible
internal class JniBindings {
    companion object {
        @JvmStatic @JniVisible external fun nCreate(surfaceControl: Long, debugName: String): Long

        @JvmStatic
        @JniVisible
        external fun nCreateFromSurface(surface: Surface, debugName: String): Long

        @JvmStatic @JniVisible external fun nRelease(surfaceControl: Long)

        @JvmStatic @JniVisible external fun nTransactionCreate(): Long

        @JvmStatic @JniVisible external fun nTransactionDelete(surfaceTransaction: Long)

        @JvmStatic @JniVisible external fun nTransactionApply(surfaceTransaction: Long)

        @JvmStatic
        @JniVisible
        external fun nTransactionReparent(
            surfaceTransaction: Long,
            surfaceControl: Long,
            newParent: Long,
        )

        @JvmStatic
        @JniVisible
        external fun nTransactionSetOnComplete(
            surfaceTransaction: Long,
            listener: SurfaceControlCompat.TransactionCompletedListener,
        )

        @JvmStatic
        @JniVisible
        external fun nTransactionSetOnCommit(
            surfaceTransaction: Long,
            listener: SurfaceControlCompat.TransactionCommittedListener,
        )

        @JvmStatic @JniVisible external fun nDupFenceFd(syncFence: SyncFenceV19): Int

        @JvmStatic
        @JniVisible
        external fun nSetBuffer(
            surfaceTransaction: Long,
            surfaceControl: Long,
            hardwareBuffer: HardwareBuffer?,
            acquireFieldFd: SyncFenceV19,
        )

        @JvmStatic
        @JniVisible
        external fun nSetGeometry(
            surfaceTransaction: Long,
            surfaceControl: Long,
            bufferWidth: Int,
            bufferHeight: Int,
            dstWidth: Int,
            dstHeight: Int,
            transformation: Int,
        )

        @JvmStatic
        @JniVisible
        external fun nSetVisibility(
            surfaceTransaction: Long,
            surfaceControl: Long,
            visibility: Byte,
        )

        @JvmStatic
        @JniVisible
        external fun nSetZOrder(surfaceTransaction: Long, surfaceControl: Long, zOrder: Int)

        @JvmStatic
        @JniVisible
        external fun nSetDamageRegion(surfaceTransaction: Long, surfaceControl: Long, rect: Rect?)

        @JvmStatic
        @JniVisible
        external fun nSetDesiredPresentTime(surfaceTransaction: Long, desiredPresentTime: Long)

        @JvmStatic
        @JniVisible
        external fun nSetBufferTransparency(
            surfaceTransaction: Long,
            surfaceControl: Long,
            transparency: Byte,
        )

        @JvmStatic
        @JniVisible
        external fun nSetBufferAlpha(surfaceTransaction: Long, surfaceControl: Long, alpha: Float)

        @JvmStatic
        @JniVisible
        external fun nSetCrop(
            surfaceTransaction: Long,
            surfaceControl: Long,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int,
        )

        @JvmStatic
        @JniVisible
        external fun nSetPosition(
            surfaceTransaction: Long,
            surfaceControl: Long,
            x: Float,
            y: Float,
        )

        @JvmStatic
        @JniVisible
        external fun nSetScale(
            surfaceTransaction: Long,
            surfaceControl: Long,
            scaleX: Float,
            scaleY: Float,
        )

        @JvmStatic
        @JniVisible
        external fun nSetBufferTransform(
            surfaceTransaction: Long,
            surfaceControl: Long,
            transformation: Int,
        )

        @JvmStatic
        @JniVisible
        external fun nSetDataSpace(surfaceTransaction: Long, surfaceControl: Long, dataSpace: Int)

        @JvmStatic @JniVisible external fun nGetDisplayOrientation(): String

        @JvmStatic @JniVisible external fun nIsHwuiUsingVulkanRenderer(): Boolean

        @JvmStatic
        @JniVisible
        external fun nGetPreviousReleaseFenceFd(surfaceControl: Long, transactionStats: Long): Int

        @JvmStatic
        @JniVisible
        external fun nSetFrameRate(
            surfaceTransaction: Long,
            surfaceControl: Long,
            frameRate: Float,
            compatibility: Int,
            changeFrameRateStrategy: Int,
        )

        init {
            System.loadLibrary("graphics-core")
        }
    }
}

/**
 * Handle to an on-screen Surface managed by the system compositor. By constructing a [Surface] from
 * this [SurfaceControlWrapper] you can submit buffers to be composited. Using
 * [SurfaceControlWrapper.Transaction] you can manipulate various properties of how the buffer will
 * be displayed on-screen. SurfaceControls are arranged into a scene-graph like hierarchy, and as
 * such any SurfaceControl may have a parent. Geometric properties like transform, crop, and
 * Z-ordering will be inherited from the parent, as if the child were content in the parents buffer
 * stream.
 *
 * Compatibility class for [SurfaceControl]. This differs by being built upon the equivalent API
 * within the Android NDK. It introduces some APIs into earlier platform releases than what was
 * initially exposed for SurfaceControl.
 */
@RequiresApi(Build.VERSION_CODES.Q)
internal class SurfaceControlWrapper {

    constructor(surfaceControl: SurfaceControlWrapper, debugName: String) {
        mNativeSurfaceControl = JniBindings.nCreate(surfaceControl.mNativeSurfaceControl, debugName)
        if (mNativeSurfaceControl == 0L) {
            throw IllegalArgumentException()
        }
    }

    constructor(surface: Surface, debugName: String) {
        mNativeSurfaceControl = JniBindings.nCreateFromSurface(surface, debugName)
        if (mNativeSurfaceControl == 0L) {
            throw IllegalArgumentException()
        }
    }

    internal var mNativeSurfaceControl: Long = 0

    /** Compatibility class for ASurfaceTransaction. */
    class Transaction() {
        private var mNativeSurfaceTransaction: Long

        init {
            mNativeSurfaceTransaction = JniBindings.nTransactionCreate()
            if (mNativeSurfaceTransaction == 0L) {
                throw java.lang.IllegalArgumentException()
            }
        }

        /**
         * Commits the updates accumulated in this transaction.
         *
         * This is the equivalent of ASurfaceTransaction_apply.
         *
         * Note that the transaction is guaranteed to be applied atomically. The transactions which
         * are applied on the same thread are als guaranteed to be applied in order.
         */
        fun commit() {
            JniBindings.nTransactionApply(mNativeSurfaceTransaction)
        }

        // Suppression of PairedRegistration below is in order to match existing
        // framework implementation of no remove method for listeners

        /**
         * Sets the callback that is invoked once the updates from this transaction are presented.
         *
         * @param listener The callback that will be invoked when the transaction has been
         *   completed. This value cannot be null.
         */
        @Suppress("PairedRegistration")
        internal fun addTransactionCompletedListener(
            listener: SurfaceControlCompat.TransactionCompletedListener
        ): Transaction {
            JniBindings.nTransactionSetOnComplete(mNativeSurfaceTransaction, listener)
            return this
        }

        /**
         * Sets the callback that is invoked once the updates from this transaction are applied and
         * ready to be presented. This callback is invoked before the setOnCompleteListener
         * callback.
         *
         * @param executor The executor that the callback should be invoked on. This value can be
         *   null, where it will run on the default thread. Callback and listener events are
         *   dispatched through this Executor, providing an easy way to control which thread is
         *   used.
         * @param listener The callback that will be invoked when the transaction has been
         *   completed. This value cannot be null.
         */
        @RequiresApi(Build.VERSION_CODES.S)
        @Suppress("PairedRegistration")
        fun addTransactionCommittedListener(
            executor: Executor?,
            listener: SurfaceControlCompat.TransactionCommittedListener,
        ): Transaction {
            var listenerWrapper = listener
            if (executor != null) {
                listenerWrapper =
                    object : SurfaceControlCompat.TransactionCommittedListener {
                        override fun onTransactionCommitted() {
                            executor.execute { (listener::onTransactionCommitted)() }
                        }
                    }
            }
            JniBindings.nTransactionSetOnCommit(mNativeSurfaceTransaction, listenerWrapper)
            return this
        }

        /**
         * Updates the [HardwareBuffer] displayed for the provided surfaceControl. Takes an optional
         * [SyncFenceV19] that is signalled when all pending work for the buffer is complete and the
         * buffer can be safely read.
         *
         * The frameworks takes ownership of the syncFence passed and is responsible for closing it.
         *
         * Note that the buffer must be allocated with [HardwareBuffer.USAGE_COMPOSER_OVERLAY] and
         * [HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE] as the surface control might be composited using
         * an overlay or the GPU.
         *
         * @param surfaceControl The surfaceControl to update. Can not be null.
         * @param hardwareBuffer The buffer to be displayed. This can not be null.
         * @param syncFence The presentation fence. If null or invalid, this is equivalent to not
         *   including it.
         */
        @JvmOverloads
        fun setBuffer(
            surfaceControl: SurfaceControlWrapper,
            hardwareBuffer: HardwareBuffer?,
            syncFence: SyncFenceV19 = SyncFenceV19(-1),
        ): Transaction {
            JniBindings.nSetBuffer(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                hardwareBuffer,
                syncFence,
            )
            return this
        }

        /** See [SurfaceControlCompat.Transaction.setDataSpace] */
        fun setDataSpace(surfaceControl: SurfaceControlWrapper, dataSpace: Int): Transaction {
            JniBindings.nSetDataSpace(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                dataSpace,
            )
            return this
        }

        /**
         * Updates the visibility of the given [SurfaceControlWrapper]. If visibility is set to
         * false, the [SurfaceControlWrapper] and all surfaces in the subtree will be hidden. By
         * default SurfaceControls are visible.
         *
         * @param surfaceControl The SurfaceControl for which to set the visibility This value
         *   cannot be null.
         * @param visibility the new visibility. A value of true means the surface and its children
         *   will be visible.
         */
        fun setVisibility(surfaceControl: SurfaceControlWrapper, visibility: Boolean): Transaction {
            JniBindings.nSetVisibility(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                if (visibility) 1 else 0,
            )
            return this
        }

        /**
         * Updates z order index for [SurfaceControlWrapper]. Note that the z order for a surface is
         * relative to other surfaces that are siblings of this surface. Behavior of siblings with
         * the same z order is undefined.
         *
         * Z orders can range from Integer.MIN_VALUE to Integer.MAX_VALUE. Default z order index
         * is 0. [SurfaceControlWrapper] instances are positioned back-to-front. That is lower z
         * order values are rendered below other [SurfaceControlWrapper] instances with higher z
         * order values.
         *
         * @param surfaceControl surface control to set the z order of.
         * @param zOrder desired layer z order to set the surfaceControl.
         */
        fun setLayer(surfaceControl: SurfaceControlWrapper, zOrder: Int): Transaction {
            JniBindings.nSetZOrder(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                zOrder,
            )
            return this
        }

        /**
         * Updates the region for content on this surface updated in this transaction. If
         * unspecified, the complete surface will be assumed to be damaged. The damage region is the
         * area of the buffer that has changed since the previously sent buffer. This can be used to
         * reduce the amount of recomposition that needs to happen when only a small region of the
         * buffer is being updated, such as for a small blinking cursor or a loading indicator.
         *
         * @param surfaceControl The surface control for which we want to set the damage region of.
         * @param region The region to set. If null, the entire buffer is assumed dirty. This is
         *   equivalent to not setting a damage region at all.
         */
        fun setDamageRegion(surfaceControl: SurfaceControlWrapper, region: Region?): Transaction {
            JniBindings.nSetDamageRegion(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                region?.bounds,
            )
            return this
        }

        /**
         * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
         * crop, visibility, and Z-ordering from their parents, as if the children were pixels
         * within the parent Surface.
         *
         * Any children of the reparented surfaceControl will remain children of the surfaceControl.
         *
         * The newParent can be null. Surface controls with a null parent do not appear on the
         * display.
         *
         * @param surfaceControl The surface control to reparent
         * @param newParent the new parent we want to set the surface control to. Can be null.
         */
        fun reparent(
            surfaceControl: SurfaceControlWrapper,
            newParent: SurfaceControlWrapper?,
        ): Transaction {
            JniBindings.nTransactionReparent(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                newParent?.mNativeSurfaceControl ?: 0L,
            )
            return this
        }

        /**
         * Specifies a desiredPresentTime for the transaction. The framework will try to present the
         * transaction at or after the time specified.
         *
         * Transactions will not be presented until all acquire fences have signaled even if the app
         * requests an earlier present time.
         *
         * If an earlier transaction has a desired present time of x, and a later transaction has a
         * desired present time that is before x, the later transaction will not preempt the earlier
         * transaction.
         *
         * @param desiredPresentTimeNano The present time in nanoseconds to try to present the
         *   Transaction at.
         */
        fun setDesiredPresentTime(desiredPresentTimeNano: Long): Transaction {
            JniBindings.nSetDesiredPresentTime(mNativeSurfaceTransaction, desiredPresentTimeNano)
            return this
        }

        /**
         * Update whether the content in the buffer associated with this surface is completely
         * opaque. If true, every pixel of content in the buffer must be opaque or visual errors can
         * occur.
         *
         * @param surfaceControl surface control to set the transparency of.
         * @param isOpaque true if buffers alpha should be ignored
         */
        fun setOpaque(surfaceControl: SurfaceControlWrapper, isOpaque: Boolean): Transaction {
            JniBindings.nSetBufferTransparency(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                if (isOpaque) 2 else 0,
            )
            return this
        }

        /**
         * Sets the alpha for the buffer. It uses a premultiplied blending.
         *
         * The passsed in alpha must be inclusively between 0.0 and 1.0.
         *
         * @param alpha alpha value within the range [0, 1].
         * @throws IllegalArgumentException if alpha is out of range.
         * @paaram surfaceControl The surface control that we want to set the alpha of.
         */
        fun setAlpha(surfaceControl: SurfaceControlWrapper, alpha: Float): Transaction {
            if (alpha < 0.0f || alpha > 1.0f) {
                throw IllegalArgumentException("Alpha value must be between 0.0 and 1.0.")
            } else {
                JniBindings.nSetBufferAlpha(
                    mNativeSurfaceTransaction,
                    surfaceControl.mNativeSurfaceControl,
                    alpha,
                )
            }
            return this
        }

        /**
         * Bounds the surface and its children to the bounds specified. Size of the surface will be
         * ignored and only the crop and buffer size will be used to determine the bounds of the
         * surface. If no crop is specified and the surface has no buffer, the surface bounds is
         * only constrained by the size of its parent bounds.
         *
         * @param surfaceControl The [SurfaceControlWrapper] to apply the crop to. This value cannot
         *   be null.
         * @param crop Bounds of the crop to apply. This value can be null. A null value will remove
         *   the crop and bounds are determined via bounds of the parent surface.
         * @throws IllegalArgumentException if crop is not a valid rectangle.
         */
        @RequiresApi(Build.VERSION_CODES.S)
        fun setCrop(surfaceControl: SurfaceControlWrapper, crop: Rect?): Transaction {
            require((crop == null) || (crop.width() >= 0 && crop.height() >= 0)) {
                throw IllegalArgumentException("width and height must be non-negative")
            }
            if (crop == null) {
                JniBindings.nSetCrop(
                    mNativeSurfaceTransaction,
                    surfaceControl.mNativeSurfaceControl,
                    0,
                    0,
                    0,
                    0,
                )
            } else {
                JniBindings.nSetCrop(
                    mNativeSurfaceTransaction,
                    surfaceControl.mNativeSurfaceControl,
                    crop.left,
                    crop.top,
                    crop.right,
                    crop.bottom,
                )
            }

            return this
        }

        /**
         * Sets the SurfaceControl to the specified position relative to the parent SurfaceControl
         *
         * @param surfaceControl The [SurfaceControlWrapper] to change position. This value cannot
         *   be null
         * @param x the X position
         * @param y the Y position
         */
        @RequiresApi(Build.VERSION_CODES.S)
        fun setPosition(surfaceControl: SurfaceControlWrapper, x: Float, y: Float): Transaction {
            JniBindings.nSetPosition(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                x,
                y,
            )
            return this
        }

        /**
         * Sets the SurfaceControl to the specified scale with (0, 0) as the center point of the
         * scale.
         *
         * @param surfaceControl The [SurfaceControlWrapper] to change scale. This value cannot be
         *   null.
         * @param scaleX the X scale
         * @param scaleY the Y scale
         */
        @RequiresApi(Build.VERSION_CODES.S)
        fun setScale(
            surfaceControl: SurfaceControlWrapper,
            scaleX: Float,
            scaleY: Float,
        ): Transaction {
            JniBindings.nSetScale(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                scaleX,
                scaleY,
            )
            return this
        }

        /**
         * Sets the buffer transform that should be applied to the current buffer
         *
         * @param surfaceControl the [SurfaceControlWrapper] to update. This value cannot be null.
         * @param transformation The transform to apply to the buffer. Value is
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY],
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL],
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_VERTICAL],
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90],
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180],
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270],
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL] |
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90], or
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_VERTICAL] |
         *   [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90]
         */
        @RequiresApi(Build.VERSION_CODES.S)
        fun setBufferTransform(
            surfaceControl: SurfaceControlWrapper,
            transformation: Int,
        ): Transaction {
            JniBindings.nSetBufferTransform(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                transformation,
            )
            return this
        }

        fun setGeometry(
            surfaceControl: SurfaceControlWrapper,
            width: Int,
            height: Int,
            dstWidth: Int,
            dstHeight: Int,
            transformation: Int,
        ): Transaction {
            JniBindings.nSetGeometry(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                width,
                height,
                dstWidth,
                dstHeight,
                transformation,
            )
            return this
        }

        fun setFrameRate(
            surfaceControl: SurfaceControlWrapper,
            frameRate: Float,
            compatibility: Int,
            changeFrameRateStrategy: Int,
        ): Transaction {
            JniBindings.nSetFrameRate(
                mNativeSurfaceTransaction,
                surfaceControl.mNativeSurfaceControl,
                frameRate,
                compatibility,
                changeFrameRateStrategy,
            )
            return this
        }

        /** Destroys the transaction object. */
        fun close() {
            if (mNativeSurfaceTransaction != 0L) {
                JniBindings.nTransactionDelete(mNativeSurfaceTransaction)
                mNativeSurfaceTransaction = 0L
            }
        }

        fun finalize() {
            close()
        }
    }

    /** Check whether this instance points to a valid layer with the system-compositor. */
    fun isValid(): Boolean = mNativeSurfaceControl != 0L

    override fun equals(other: Any?): Boolean {
        if (other == this) {
            return true
        }
        if ((other == null) or (other?.javaClass != SurfaceControlWrapper::class.java)) {
            return false
        }

        other as SurfaceControlWrapper
        if (other.mNativeSurfaceControl == this.mNativeSurfaceControl) {
            return true
        }

        return false
    }

    override fun hashCode(): Int {
        return mNativeSurfaceControl.hashCode()
    }

    /**
     * Release the local reference to the server-side surface. The surface may continue to exist
     * on-screen as long as its parent continues to exist. To explicitly remove a surface from the
     * screen use [Transaction.reparent] with a null-parent. After release, [isValid] will return
     * false and other methods will throw an exception. Always call release() when you're done with
     * a SurfaceControl.
     */
    fun release() {
        if (mNativeSurfaceControl != 0L) {
            JniBindings.nRelease(mNativeSurfaceControl)
            mNativeSurfaceControl = 0
        }
    }

    protected fun finalize() {
        release()
    }

    /**
     * Builder class for [SurfaceControlWrapper].
     *
     * Requires a debug name.
     */
    class Builder {
        private var mSurface: Surface? = null
        private var mSurfaceControl: SurfaceControlWrapper? = null
        private lateinit var mDebugName: String

        fun setParent(surface: Surface): Builder {
            mSurface = surface
            mSurfaceControl = null
            return this
        }

        fun setParent(surfaceControlWrapper: SurfaceControlWrapper): Builder {
            mSurface = null
            mSurfaceControl = surfaceControlWrapper
            return this
        }

        @Suppress("MissingGetterMatchingBuilder")
        fun setDebugName(debugName: String): Builder {
            mDebugName = debugName
            return this
        }

        /** Builds the [SurfaceControlWrapper] object */
        fun build(): SurfaceControlWrapper {
            val surface = mSurface
            val surfaceControl = mSurfaceControl
            return if (surface != null) {
                SurfaceControlWrapper(surface, mDebugName)
            } else if (surfaceControl != null) {
                SurfaceControlWrapper(surfaceControl, mDebugName)
            } else {
                throw IllegalStateException("")
            }
        }
    }
}
